JAVA | 多线程断点下载

基本原理:利用URLConnection获取要下载文件的长度、头部等相关信息,并设置响应的头部信息。并且通过URLConnection获取输入流,将文件分成指定的块,每一块单独开辟一个线程完成数据的读取、写入。通过输入流读取下载文件的信息,然后将读取的信息用RandomAccessFile随机写入到本地文件中。同时,每个线程写入的数据都文件指针也就是写入数据的长度,需要保存在一个临时文件中。这样当本次下载没有完成的时候,下次下载的时候就从这个文件中读取上一次下载的文件长度,然后继续接着上一次的位置开始下载。并且将本次下载的长度写入到这个文件中。

最简单的下载代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.xian.blog;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class SimpleDownload {
public static void main(String[] args) {
try {
URL url = new URL("http://ww1.sinaimg.cn/large/adc90466gy1fcptk0wnt2j20e90ifmz4");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
int code = conn.getResponseCode();
if (200 == code) {
BufferedInputStream in = new BufferedInputStream(conn.getInputStream());
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("/Users/xianxiaotao/desktop/activitylife.png"));
byte[] buf = new byte[1024];
int len = -1;
while (-1 != (len = in.read(buf)))
out.write(buf, 0, len);
in.close();
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

上述代码简易,但不适合大型文件下载。

多线程断点下载代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package com.xian.blog;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class XDownloadTask extends Thread{
private static final String DIR_PATH = "/Users/xianxiaotao/downloads"; // 下载目录
private static final int THREAD_AMOUNT = 3; // 总线程数
private URL url; // 目标下载地址
private File dataFile; // 本地文件
private File tempFile; // 存储线程下载进度的临时文件
private long totalLength; // 服务端文件总长度
private long threadLenght; // 每个线程要下载的长度
private long totalFinish; // 记录:总共完成了多少
private long begin; // 用来记录下载开始时间
public XDownloadTask(String address) throws IOException {
url = new URL(address); // 记住下载地址
dataFile = new File(DIR_PATH, address.substring(address.lastIndexOf("/") + 1)); // 截取地址中的文件名,创建本地文件
tempFile = new File(dataFile.getAbsolutePath() + ".temp"); // 在本地文件所在文件夹中创建临时文件
}
public void run() {
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10000);
totalLength = conn.getContentLength(); // 获取服务端发送过来的文件长度
threadLenght = (totalLength + THREAD_AMOUNT - 1) / THREAD_AMOUNT; // 计算每个线程要下载的长度
if (!tempFile.exists()) { // 如果临时文件不存在
RandomAccessFile raf = new RandomAccessFile(tempFile, "rw"); // 创建临时文件,用来记录每个线程已下载多少
for (int i = 0; i < THREAD_AMOUNT; i++)
raf.writeInt(0);
raf.close();
}
for (int i = 0; i < THREAD_AMOUNT; i++) // 按线程数循环
new DownloadThread(i).start(); // 开启线程,每个线程将会下载一部分数据到本地文件中
begin = System.currentTimeMillis(); // 记录开始时间
} catch (IOException e) {
e.printStackTrace();
}
}
private class DownloadThread extends Thread {
private int id; // 用来标记当前线程是下载任务中的第几个线程
public DownloadThread(int id) {
this.id = id;
}
public void run() {
try {
RandomAccessFile tempRaf = new RandomAccessFile(tempFile, "rws"); // 记录进度的临时文件
tempRaf.seek(id * 4); // 将指针移动到当前线程的位置
int threadFinish = tempRaf.readInt(); // 读取当前线程已完成了多少
synchronized(XDownloadTask.this) { // 多个下载线程之间同步
totalFinish += threadFinish; // 统计所有线程总共完成了多少
}
long start = id * threadLenght + threadFinish; // 计算当前线程起始位置
long end = id * threadLenght + threadLenght - 1; // 计算当前线程结束位置
System.out.println("线程" + id + ": " + start + "-" + end);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10000);
conn.setRequestProperty("Range", "bytes=" + start + "-" + end); // 设置当前线程下载的范围
BufferedInputStream in = new BufferedInputStream(conn.getInputStream());// 获取连接的输入流
RandomAccessFile dataRaf = new RandomAccessFile(dataFile, "rws"); // 保存数据的本地文件
dataRaf.seek(start); // 设置当前线程保存数据的位置
byte[] buf = new byte[1024];
int len = -1;
while (-1 != (len = in.read(buf))) {
dataRaf.write(buf, 0, len);
threadFinish += len;
tempRaf.seek(id * 4);
tempRaf.writeInt(threadFinish); // 将当前线程完成了多少写入到临时文件
synchronized (XDownloadTask.this) {
totalFinish += len;
}
}
dataRaf.close();
tempRaf.close();
System.out.println("线程" + id + "下载完毕");
if (totalFinish == totalLength) {
System.out.println("下载完成,耗时:" + (System.currentTimeMillis() - begin));
tempFile.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
new XDownloadTask("http://ww1.sinaimg.cn/large/adc90466gy1fcptk0wnt2j20e90ifmz4").start();
}
}